using System;
using System.Collections.Generic;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Windows.Forms;

namespace SmsServerClient
{
    enum MessageTypeEnum { Sms, Command, DeliveryReport, UnKnown };
    enum CommandTypeEnum { StartFilter, StopFilter, CloseApplication, SetValue, GetInboxMessage };
    enum DeliveryStatusEnum { Sent, Failed };
    enum Delim { Field = 1, Packet = 2 };
    enum Constants { MaxMessageLength = 4096 };
    public class SmsClass
    {
        public string Number;
        public string Body;
        public DateTime Time;
        public Guid MessageID;
        public SmsClass()
        {
            Number = "";
            Body = "";
            Time = DateTime.Now;
            MessageID = Guid.NewGuid();
        }

        public SmsClass(string[] Fields)
        {
            Number = Fields[1];
            Body = Fields[2];
            Time = new DateTime(Convert.ToInt64(Fields[3]));
            MessageID = new Guid(Fields[4]);
        }
        public int Sizeof()
        {
            int length = 0;
            length += Number.ToString().Length + 1;
            length += Body.Length + 1;
            length += Time.ToString().Length + 1;
            length += MessageID.ToString().Length + 1;
            return length;
        }
        public override string ToString()
        {

            String str = ((int)MessageTypeEnum.Sms).ToString() + Convert.ToChar(Delim.Field) +
                Number.ToString() + Convert.ToChar(Delim.Field) +
                Body.ToString() + Convert.ToChar(Delim.Field) +
                Time.Ticks.ToString() + Convert.ToChar(Delim.Field) +
                MessageID.ToString();
            return str;
        }

    }


    class CommandClass
    {
        public CommandTypeEnum Type;
        public string Name;
        public string Value;
        public CommandClass()
        {
            Name = "";
            Value = "";
            Type = CommandTypeEnum.SetValue;
        }

        public CommandClass(string[] Fields)
        {
            Name = Fields[1];
            Value = Fields[2];
            Type = (CommandTypeEnum)Convert.ToInt16(Fields[3]);
        }
        public override string ToString()
        {
            String str = ((int)MessageTypeEnum.Command).ToString() + Convert.ToChar(Delim.Field) +
                Name.ToString() + Convert.ToChar(Delim.Field) +
                Value.ToString() + Convert.ToChar(Delim.Field) +
                ((int)Type).ToString();
            return str;
        }
    }

    class DeliveryReportClass
    {
        public Guid MessageID;
        public DeliveryStatusEnum Status;
        public DeliveryReportClass()
        {
            Status = DeliveryStatusEnum.Sent;
            MessageID = Guid.NewGuid();
        }
        public DeliveryReportClass(string[] Fields)
        {
            Status = (DeliveryStatusEnum)Convert.ToInt16(Fields[1]);
            MessageID = new Guid(Fields[2]);
        }
        public override string ToString()
        {
            String str = ((int)MessageTypeEnum.DeliveryReport).ToString() + Convert.ToChar(Delim.Field) +
                ((int)Status).ToString() + Convert.ToChar(Delim.Field) +
                MessageID.ToString();
            return str;
        }
    }

    class TcpSocketClass
    {
        public AsyncCallback pfnWorkerCallBack;
        private Socket m_mainSocket;
        private Socket WorkerSocket;
        public int IsConnected;
        public int PortInUse;
        public delegate void MessageReceivedDelegate(string Message);
        public event MessageReceivedDelegate MessageReceived;
        public delegate void SmsReceivedDelegate(SmsClass Message);
        public event SmsReceivedDelegate SmsReceived;
        public delegate void CommandReceivedDelegate(CommandClass Command);
        public event CommandReceivedDelegate CommandReceived;
        public delegate void DeliveryReportReceivedDelegate(DeliveryReportClass Command);
        public event DeliveryReportReceivedDelegate DeliveryReportReceived;
        public delegate void ConnectedDelegate();
        public event ConnectedDelegate Connected;
        public delegate void DisconnectedDelegate(bool ShouldRestart);
        public event DisconnectedDelegate Disconnected;

        Byte[] Data;
        int Offset;

        public TcpSocketClass(Int16 port)
        {
            try
            {
                m_mainSocket = new Socket(AddressFamily.InterNetwork,
                    SocketType.Stream,
                    ProtocolType.Tcp);
                IPEndPoint ipLocal = new IPEndPoint(IPAddress.Any, port);
                // Bind to local IP Address...
                m_mainSocket.Bind(ipLocal);
                // Start listening...
                m_mainSocket.Listen(1);
                // Create the call back for any client connections...
                WorkerSocket = null;
                Data = new byte[(int)Constants.MaxMessageLength];
                Interlocked.Exchange(ref IsConnected, 0);
                Interlocked.Exchange(ref Offset, 0);
                PortInUse = port;
            }
            catch (Exception ex)
            {
                ConnectForm.WriteLogDB("Ex code 107:" + ex.Message);
                m_mainSocket = null;
                PortInUse = -1;
            }

        }

        internal bool StartListener()
        {
            bool Success = false;
            try
            {
                if (m_mainSocket == null)
                    throw new Exception("Listener Socket null....");
                if (WorkerSocket != null)
                    throw new Exception("Worker socket not null..");
                m_mainSocket.BeginAccept(new AsyncCallback(OnClientConnect), null);
                Success = true;
            }
            catch(Exception ex)
            {
                MessageBox.Show("Please report this error to vibhoregoyal@gmail.com:" + ex.Message);
                ConnectForm.WriteLogDB("Ex code 106:" + ex.Message);
                Success = false;
            }
            return Success;
        }

        public void OnClientConnect(IAsyncResult asyn)
        {
            try
            {
                WorkerSocket = m_mainSocket.EndAccept(asyn);
                Interlocked.Exchange(ref Offset, 0);
                Interlocked.Exchange(ref IsConnected, 1);
                WaitForData(WorkerSocket);
                if (Connected != null)
                    Connected();
            }
            catch(Exception ex)
            {
                MessageBox.Show("Please report this error to vibhoregoyal@gmail.com:" + ex.Message);
                ConnectForm.WriteLogDB("Ex code 109:" + ex.Message);
                Interlocked.Exchange(ref IsConnected, 0);
            }
        }

        public void WaitForData(System.Net.Sockets.Socket soc)
        {
            if (soc != null && soc.Connected)
            {
                try
                {
                    if (pfnWorkerCallBack == null)
                        pfnWorkerCallBack = new AsyncCallback(OnDataReceived);
                    soc.BeginReceive(Data, Offset,
                        (int)Constants.MaxMessageLength - Offset,
                        SocketFlags.None, pfnWorkerCallBack, null);
                }
                catch(Exception ex)
                {
                    ConnectForm.WriteLogDB("Ex code 103:" + ex.Message);
                    Disconnect(true);
                }
            }
        }

        // This the call back function which will be invoked when the socket
        // detects any client writing of data on the stream
        public void OnDataReceived(IAsyncResult asyn)
        {
            if (WorkerSocket != null && WorkerSocket.Connected)
            {
                try
                {
                    int iRx = WorkerSocket.EndReceive(asyn);

                    byte EndChar = Convert.ToByte(Delim.Packet);
                    char FieldChar = Convert.ToChar(Delim.Field);
                    for (int i = 0; i < iRx; i++)
                    {
                        if (Data[Offset] == EndChar)
                        {
                            char[] chars = new char[Offset];
                            // Extract the characters as a buffer
                            System.Text.Decoder d = System.Text.Encoding.UTF8.GetDecoder();
                            int charLen = d.GetChars(Data, 0, Offset, chars, 0);
                            System.String szData = new System.String(chars);
                            String[] sFields = szData.Split(FieldChar);
                            try
                            {
                                int PacketSize = int.Parse(sFields[sFields.Length - 1]);
                                if (PacketSize == szData.Length - sFields[sFields.Length - 1].Length)
                                {
                                    MessageTypeEnum MessageType;
                                    try
                                    {
                                        if (sFields.Length > 2)
                                            MessageType = (MessageTypeEnum)int.Parse(sFields[0]);
                                        else
                                            MessageType = MessageTypeEnum.UnKnown;
                                    }
                                    catch(Exception ex)
                                    {
                                        ConnectForm.WriteLogDB("Ex code 110:" + ex.Message);
                                        MessageType = MessageTypeEnum.UnKnown;
                                    }
                                    if (MessageType == MessageTypeEnum.Sms)
                                    {
                                        if (SmsReceived != null)
                                            SmsReceived(new SmsClass(sFields));
                                    }
                                    else if (MessageType == MessageTypeEnum.Command)
                                    {
                                        if (CommandReceived != null)
                                            CommandReceived(new CommandClass(sFields));
                                    }
                                    else if (MessageType == MessageTypeEnum.DeliveryReport)
                                    {
                                        if (DeliveryReportReceived != null)
                                            DeliveryReportReceived(new DeliveryReportClass(sFields));
                                    }
                                    else
                                    {
                                        if (MessageReceived != null)
                                            MessageReceived(szData);
                                    }
                                }
                            }
                            catch  (Exception ex)
                            {
                                ConnectForm.WriteLogDB("Ex code 102:" + ex.Message);
                            }
                            for (int j = 1; j < iRx - i; j++)
                                Data[j-1] = Data[Offset + j];
                            Interlocked.Exchange(ref Offset, 0);
                        }
                        else
                            Interlocked.Increment(ref Offset);

                    }
                    WaitForData(WorkerSocket);
                }
                catch(Exception ex)
                {
                    ConnectForm.WriteLogDB("Ex code 101:" + ex.Message);
                    Disconnect(true);
                }
            }
        }

        public void SendMessage(string Msg)
        {
            if (WorkerSocket != null && WorkerSocket.Connected)
            {
                try
                {
                    NetworkStream networkStream = new NetworkStream(WorkerSocket);
                    System.IO.StreamWriter streamWriter = new System.IO.StreamWriter(networkStream);
                    Msg += Convert.ToChar(Delim.Field) + (Msg.Length + 1).ToString() + Convert.ToChar(Delim.Packet);
                    streamWriter.Write(Msg);
                    streamWriter.Flush();
                }
                catch(Exception ex)
                {
                    ConnectForm.WriteLogDB("Ex code 104:" + ex.Message);
                    Disconnect(true);
                }
            }
        }
        public bool SendData(Byte[] data, int size)
        {
            try
            {
                if (IsConnected == 1 && WorkerSocket != null && WorkerSocket.Connected)
                {
                    WorkerSocket.Send(data, size, SocketFlags.None);
                }
            }
            catch(Exception ex)
            {
                ConnectForm.WriteLogDB("Ex code 105:" + ex.Message);
                Disconnect(true);
            }
            return (IsConnected == 1 ? true : false);
        }

        internal void Disconnect(bool p)
        {
            if (WorkerSocket != null)
            {
                WorkerSocket.Close();
                WorkerSocket = null;
                pfnWorkerCallBack = null;
            }
            Interlocked.Exchange(ref IsConnected, 0);
            if (Disconnected != null)
                Disconnected(p);
        }

    }
}
